/*
 *   Copyright (C) 2009 Ultratronik Entwicklungs GmbH 
 *
 *     - Author: Sören Wellhöfer <soeren.wellhoefer@gmx.net>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *
 * Compilation:
 *   gcc -o logeval logeval.c
 *
 * Abstract:
 *   This program works on UtraBench logfiles to extract,
 *   summarize, and evaluate collected data. UltraBench is
 *   a network connection benchmarking utility.
 *   UltraBench logfiles contain continuous lines of
 *   of numeric values; one line in such a logfile
 *   is exactly that what was measured in one second.
 *   Usually UltraBench writes its logfiles to /var/log/ubench.
 *
 * Example usage:
 * 
 * $ logeval -summary tcp /var/log/ubench
 *  system: 
 *    avrg. cpu     = 57.84 % 
 *    avrg. mem     = 19.97 % 
 *    avrg. swap    = 0.00 % 
 *  transfer: 
 *    avrg. rate    = 76634697.92 bit/sec (76.63 Mbit/sec) 
 *    total         = 1049151000 bit (1000.55 Mbyte) 
 *    time          = 2154 sec (35.90 min) 
 *    s11n          = 526.30 us 
 *  tcp: 
 *    avrg. rtt     = 0.55 ms 
 *    avrg. jitter  = 0.57 ms 
 *    total         = 2131432024 bits (1.99 Gbyte) 
 *    packets       = 842507 
 *    packets/sec   = 390.41 
 *
 *
 *  By using the -ordinate functor it is possible to create a 
 *  function-like domain/range association between distinct 
 *  quantities recorded in different logfiles.
 *
 * $ logeval -ordinate tcp/rtt:cpu -interval 60 -stubs 40,290,530,870:2 /var/log/ubench
 *  cpu		tcp/rtt 
 *  83.95	1.36 
 *  87.80	0.59 
 *  89.32	1.16 
 *  93.60	0.60 
 *  93.17	1.42 
 *  93.62	1.16 
 *  62.05	0.59 
 *  60.87	0.23  
 *
 *  The stubs represent points in time (seconds) at which a measuring 
 *  session of <interval length> had been initiated. The value given after 
 *  the colon in the stubs command line argument denotes how many successive 
 *  intervals of <interval length> at each stub shall be evaluated by logeval.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>

/* Constants */
const char *usg = "UltraBench logfile analysis tool\n"
"usage: %s FUNCTOR [OPTIONS] LOGDIR\n\n"
"    FUNCTORs  =  -summary   [tcp|uart|can,...]\n"
"                 -average   LOG\n"
"                 -ordinate  DEPENDENT:VARIABLE\n\n"
"    OPTIONS   =  -interval  INTRVL\n"
"                 -stubs     STUBLIST[:TURNS]\n"
"                 -start     SECONDS\n"	
"                 -end       SECONDS\n"
"                 -out       FILE\n"
"                 -nohead\n"
"                 -verbose\n\n"	
"    STUBLIST  =  VAL1,...,VALn\n\n"
"    Note: Functors mutually exclusive.\n\n";
const char *prog;

#define DEFAULT_INTERVAL 60 /*sec*/
#define DEFAULT_MAX 0

#define BYTE_KILO 1024
#define KILO 1000

/* Macros */
#define min(a, b) ((a) < (b) ? (a) : (b)) 

/* Gobals */
const char *logdir;
const char *stublist;
int end, interval, turns, start;
FILE *outf;
int flags;

/* Definitions */
enum {
	FUNCTOR_SUMMARY = 1<<0,
	FUNCTOR_AVRG = 1<<1,
	FUNCTOR_ORDINATE = 1<<2,

	FLAG_NOHEADERS = 1<<0,
	FLAG_VERBOSE = 1<<1,
	FLAG_SUMTCP = 1<<2,
	FLAG_SUMUART = 1<<3,
	FLAG_SUMCAN = 1<<4,
};

struct {
	const char *idf;
	int flag;	
} summary_flags[] =
	{{"tcp", FLAG_SUMTCP},
	 {"uart", FLAG_SUMUART},
	 {"can", FLAG_SUMCAN}};

/* Prototypes */
static void* memalloc(size_t);

/* Implementation */
static inline void usage()
{
	printf(usg, prog);
}

static void errmsg(char *frm, int with_errno, ...)
{
	static const char *err = "error";
	static const char *format = "%s: %s\n";
	size_t len;
	char *pre;
	va_list l;

	va_start(l, with_errno);

	len = strlen(err) + strlen(format) + strlen(frm);
	pre = (char*)memalloc((len + 1) * sizeof(char));
	pre[len] = 0;
	
	sprintf(pre, format, err, frm);
	vfprintf(stderr, pre, l);

	if(with_errno) fprintf(stderr, "(%s)\n", strerror(errno));

	free(pre);
	va_end(l);
}

static void* memalloc(size_t s)
{
	void *mem = (void*)malloc(s);
	if(!mem) {
		fprintf(stderr, "out of memory\n");
		exit(-1);
	}
	return mem;
}

static inline int unq(int v) {
	register int r = 0;
	for(; v; v >>= 1) r ^= v & 1;
	return !r;
}

static inline long double convert(long double a, const char** unit, int byte)
{
	long double b;
	float div = byte ? BYTE_KILO : KILO;
	
	if((b = a / div) < 1) {
		*unit = "";
		return a;
	} else if((a = b / div) < 1) {
		*unit = "k";
		return b;
	} else if((b = a / div) < 1) {
		*unit = "M";
		return a;
	} else if((a = b / div) < 1) {
		*unit = "G";
		return b;
	}

	*unit = "T";
	return a;
}

static void read_data(const char *logfile, long double **data, int *size)
{
	char fname[512], buf[64];
	FILE *log;

	sprintf(fname, "%s/%s", logdir, logfile);

	if((log = fopen(fname, "r")) == NULL) {
		errmsg("unable to open %s", 1, fname);
		exit(-1);
	}

	for(*size = 1; fgets(buf, sizeof buf, log); ) {

		if(*size < start) {
			(*size)++;
			continue;
		} else if(*size == start) {
			*size = 1;
		}
		
		if(end && *size >= end) goto end;
		*data = realloc(*data, *size * sizeof(long double));
		if(sscanf(buf, "%Lf", &(*data)[*size-1]) != 1) {
			errmsg("error reading from %s", 1, fname);
			exit(-1);
		}
		
		if((*data)[*size-1] > 0) (*size)++;
	}

 end:
	fclose(log);
}

static inline long double last(const char *log, int *size)
{
	long double ret, *data = NULL;
	
	read_data(log, &data, size);
	ret = data[*size-2];
	free(data);
	
	return ret;
}

static long double average(const char *log)
{
	long double *data = NULL;
	long double sum = 0;
	int size, cnt;
	
	read_data(log, &data, &size);
	cnt = size;

	while(--size > 0) sum += data[size-1];

	free(data);
	return sum / (double)cnt;
}

static void summary()
{
	int size;
	long double val, conv;
	const char *rate_unit, *total_unit, *tcp_total_unit;
	
	fprintf(outf, "system:\n");
	fprintf(outf, "  avrg. cpu     = %.2Lf %%\n", average("cpu"));
	fprintf(outf, "  avrg. mem     = %.2Lf %%\n", average("mem"));
	fprintf(outf, "  avrg. swap    = %.2Lf %%\n", average("swap"));
	
	fprintf(outf, "transfer:\n");
	val = average("rate");
	conv = convert(val, &rate_unit, 0);
	fprintf(outf, "  avrg. rate    = %.2Lf bit/sec (%.2Lf %sbit/sec)\n", val, conv, rate_unit);
	val = last("total", &size);
	conv = convert(val, &total_unit, 1);
	fprintf(outf, "  total         = %ld bit (%.2Lf %sbyte)\n", (long)val, conv, total_unit);
	fprintf(outf, "  time          = %d sec (%.2f min)\n", size, (float)size / 60.0);
	val = average("s11n");
	fprintf(outf, "  s11n          = %.2f us\n", (float)val);

	if(flags & FLAG_SUMTCP) {
		fprintf(outf, "tcp:\n");
		val = average("tcp/rtt");
		fprintf(outf, "  avrg. rtt     = %.2f ms\n", (float)val);
		val = average("tcp/jitter");
		fprintf(outf, "  avrg. jitter  = %.2f ms\n", (float)val);
		
		val = last("tcp/rawtotal", &size);
		conv = convert(val, &tcp_total_unit, 1);
		fprintf(outf, "  total         = %ld bits (%.2Lf %sbyte)\n", (long)val, conv, tcp_total_unit);
		val = last("tcp/packets", &size);
		fprintf(outf, "  packets       = %ld\n", (long)val);
		fprintf(outf, "  packets/sec   = %.2Lf\n", val / size);
	}

	if(flags & FLAG_SUMUART) {

	}

	if(flags & FLAG_SUMCAN) {

	}
}

static void ordinate(char *arg)
{
	char *dep_log = arg, *var_log;

	var_log = strchr(arg, ':') + 1;
	var_log[-1] = 0;
	
	if(!var_log) {
		errmsg("invalid ordination parameter %s", 0, arg);
		exit(-1);
	}

	long double *dep_data, *var_data;
	int dep_size, var_size;
	dep_data = var_data = NULL;

	read_data(dep_log, &dep_data, &dep_size);
	read_data(var_log, &var_data, &var_size);
	
	int read, cur, div, nextstub;
	long double dep_sum, var_sum;
	register int i, t = 1;
	char buf[16];

	if(!(flags & FLAG_NOHEADERS))
		fprintf(outf, "%s\t%s\n", var_log, dep_log);
	
	for(nextstub = 0;;) {
		
		cur = nextstub;
	
		if(stublist && (!nextstub || t >= turns)) {
			if(sscanf(stublist, "%16[^,]%n", buf, &read) == 1) {
				int n = atoi(buf);
				if(nextstub && (n - nextstub) < interval) {
					errmsg("invalid stub %s; check interval (%d to %d)", 0, buf,
					       nextstub, n);
					exit(-1);
				} else
					nextstub = n;
				if(*(stublist += read)) stublist++;
			} else
				break;
			t = 1;
		} else {
			nextstub += interval;
			if(stublist) t++;
		}

		i = div = cur;
		cur += interval;
		if(cur >= dep_size || cur >= var_size)
			cur = min(dep_size, var_size);

		if((div = cur - div) <= 0) break;
		
		for(dep_sum = var_sum = 0; i <= cur; i++) {
			dep_sum += dep_data[i];
			var_sum += var_data[i];
		}

		fprintf(outf, "%.2Lf\t%.2Lf", var_sum / div, dep_sum / div);
		if(flags & FLAG_VERBOSE) fprintf(outf, "\t(%d to %d)", nextstub, cur);
		fputc('\n', outf);
	}

	free(dep_data);
	free(var_data);
}

int main(int argc, char **argv)
{
	int fct = 0;
	char *arg;
	
	prog = *argv++;
	
	if(argc < 3) {
		usage();
		return -1;
	}

	end = DEFAULT_MAX;
	interval = DEFAULT_INTERVAL;
	outf = stdout;
	stublist = NULL;
	flags = 0;
	turns = 1;
	
	for( ; --argc > 0; argv++) {

		if(!strcmp("-h", *argv) || !strcmp("-help", *argv)) {
			usage();
			return 0;
			
		} else if(!strcmp("-summary", *argv)) {
			fct |= FUNCTOR_SUMMARY;
			if(argc > 1 && **(argv + 1) != '-') {
				char *w = *++argv;
				register int i;
				int fnd = 0;
				for(; w; w = strchr(w, ','), w ? w++ : w) {
					for(i = 0; i < 3; i++)
						if(!strncmp(summary_flags[i].idf, w, strlen(summary_flags[i].idf))) {
							flags |= summary_flags[i].flag;
							fnd = 1;
						}
				}
				if(fnd) --argc;
				else --argv;
			}
		} else if(!strcmp("-average", *argv)) {
			fct |= FUNCTOR_AVRG;
			arg = *++argv;
			--argc;
		} else if(!strcmp("-ordinate", *argv)) {
			fct |= FUNCTOR_ORDINATE;
			arg = *++argv;
			--argc;
		} else if(!strcmp("-stubs", *argv)) {
			char *p;
			if((p = strchr(*++argv, ':'))) {
				*p++ = '\0';
				if(!(turns = atoi(p))) {
					errmsg("there must be at least one turn", 0);
					return -1;
				}
			}
			stublist = *argv;
			--argc;
		} else if(!strcmp("-end", *argv)) {
			if(!(end = atoi(*++argv))) {
				errmsg("invalid end value %s", 0, *argv);
				return -1;
			}
			--argc;
		} else if(!strcmp("-start", *argv)) {
			if(!(start = atoi(*++argv))) {
				errmsg("invalid start value %s", 0, *argv);
				return -1;
			}
			--argc;
		} else if(!strcmp("-interval", *argv)) {
			if(!(interval = atoi(*argv))) {
				errmsg("interval must be > 0", 0, *argv);
				return -1;
			}
			--argc;	
		} else if(!strcmp("-out", *argv)) {
			if(!(outf = fopen(*++argv, "w"))) {
				errmsg("cannot open for %s for writing", 1, *argv);
				return -1;
			}
			--argc;
		} else if(!strcmp("-nohead", *argv)) {
			flags |= FLAG_NOHEADERS;
		} else if(!strcmp("-verbose", *argv)) {
			flags |= FLAG_VERBOSE;
		} else {
			logdir = *argv;
		}
	}
	
	if(unq(fct)) {
		errmsg("functors erroneously given", 0);
		return -1;
	}

	if(!logdir) {
		errmsg("logfile directory not given", 0);
		return -1;
	}
	
	if(fct & FUNCTOR_AVRG) 
		fprintf(outf, "%s: avrg = %.2Lf\n", arg, average(arg));
	else if(fct & FUNCTOR_SUMMARY)
		summary();
	else if(fct & FUNCTOR_ORDINATE)
		ordinate(arg);
	
	return 0;
}

